home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / DistUpgrade / DistUpgradeAufs.py < prev    next >
Text File  |  2009-11-02  |  10KB  |  251 lines

  1. import string
  2. import logging
  3. import os
  4. import os.path
  5. import subprocess
  6. import tempfile
  7.  
  8. # dirs that the packages will touch
  9. systemdirs = ["/bin",
  10.               "/boot",
  11.               "/etc",
  12.               "/initrd",
  13.               "/lib",
  14.               "/lib32", # ???
  15.               "/sbin",
  16.               "/usr",
  17.               "/var"]
  18.  
  19.  
  20. def aufsOptionsAndEnvironmentSetup(options, config):
  21.     """ setup the environment based on the config and options
  22.     It will use
  23.     config("Aufs","Enabled") - to show if its enabled
  24.     and
  25.     config("Aufs","RWDir") - for the writable overlay dir
  26.     """
  27.     logging.debug("aufsOptionsAndEnvironmentSetup()")
  28.     # enabled from the commandline (full overlay by default)
  29.     if options and options.useAufs:
  30.         logging.debug("enabling full overlay from commandline")
  31.         config.set("Aufs","Enabled", True)
  32.         config.set("Aufs","EnableFullOverlay",True)
  33.     
  34.     # setup environment based on config
  35.     tmprw = tempfile.mkdtemp(prefix="upgrade-rw-")
  36.     aufs_rw_dir = config.getWithDefault("Aufs","RWDir", tmprw)
  37.     logging.debug("using '%s' as aufs_rw_dir" % aufs_rw_dir)
  38.     os.environ["RELEASE_UPGRADE_AUFS_RWDIR"] = aufs_rw_dir
  39.     config.set("Aufs","RWDir",aufs_rw_dir)
  40.     # now the chroot tmpdir
  41.     tmpchroot = tempfile.mkdtemp(prefix="upgrade-chroot-")
  42.     os.chmod(tmpchroot, 0755)
  43.     aufs_chroot_dir = config.getWithDefault("Aufs","ChrootDir", tmpchroot)
  44.     logging.debug("using '%s' as aufs chroot dir" % aufs_chroot_dir)
  45.     
  46.     if config.getWithDefault("Aufs","EnableFullOverlay", False):
  47.         logging.debug("enabling aufs full overlay (from config)")
  48.         config.set("Aufs","Enabled", True)        
  49.         os.environ["RELEASE_UPGRADE_USE_AUFS_FULL_OVERLAY"] = "1"
  50.     if config.getWithDefault("Aufs","EnableChrootOverlay",False):
  51.         logging.debug("enabling aufs chroot overlay")
  52.         config.set("Aufs","Enabled", True)        
  53.         os.environ["RELEASE_UPGRADE_USE_AUFS_CHROOT"] = aufs_chroot_dir
  54.     if config.getWithDefault("Aufs","EnableChrootRsync",False):
  55.         logging.debug("enable aufs chroot rsync back to real system")
  56.         os.environ["RELEASE_UPGRADE_RSYNC_AUFS_CHROOT"] = "1"
  57.     
  58.  
  59. def _bindMount(from_dir, to_dir, rbind=False):
  60.     " helper that bind mounts a given dir to a new place "
  61.     if not os.path.exists(to_dir):
  62.         os.makedirs(to_dir)
  63.     if rbind:
  64.         bind = "--rbind"
  65.     else:
  66.         bind = "--bind"
  67.     cmd = ["mount",bind, from_dir, to_dir]
  68.     logging.debug("cmd: %s" % cmd)
  69.     res = subprocess.call(cmd)
  70.     if res != 0:
  71.         # FIXME: revert already mounted stuff
  72.         logging.error("Failed to bind mount from '%s' to '%s'" % (from_dir, to_dir))
  73.         return False
  74.     return True
  75.  
  76. def _aufsOverlayMount(target, rw_dir, chroot_dir="/"):
  77.     """ 
  78.     helper that takes a target dir and mounts a rw dir over it, e.g.
  79.     /var , /tmp/upgrade-rw
  80.     """
  81.     if not os.path.exists(rw_dir+target):
  82.         os.makedirs(rw_dir+target)
  83.     if not os.path.exists(chroot_dir+target):
  84.         os.makedirs(chroot_dir+target)
  85.     cmd = ["mount",
  86.            "-t","aufs",
  87.            "-o","br:%s:%s=ro" % (rw_dir+target, target),
  88.            "none",
  89.            chroot_dir+target]
  90.     res = subprocess.call(cmd)
  91.     if res != 0:
  92.         # FIXME: revert already mounted stuff
  93.         logging.error("Failed to mount rw aufs overlay for '%s'" % target)
  94.         return False
  95.     logging.debug("cmd '%s' return '%s' " % (cmd, res))
  96.     return True
  97.  
  98. def is_aufs_mount(dir):
  99.     " test if the given dir is already mounted with aufs overlay "
  100.     for line in open("/proc/mounts"):
  101.         (device, mountpoint, fstype, options, a, b) = line.split()
  102.         if device == "none" and fstype == "aufs" and mountpoint == dir:
  103.             return True
  104.     return False
  105.  
  106. def is_submount(mountpoint, systemdirs):
  107.     " helper: check if the given mountpoint is a submount of a systemdir "
  108.     logging.debug("is_submount: %s %s" % (mountpoint, systemdirs))
  109.     for d in systemdirs:
  110.         if not d.endswith("/"):
  111.             d += "/"
  112.         if mountpoint.startswith(d):
  113.             return True
  114.     return False
  115.  
  116. def is_real_fs(fs):
  117.     if fs.startswith("fuse"):
  118.         return False
  119.     if fs in ["rootfs","tmpfs","proc","fusectrl","aufs",
  120.               "devpts","binfmt_misc", "sysfs"]:
  121.         return False
  122.     return True
  123.  
  124. def doAufsChrootRsync(aufs_chroot_dir):
  125.     """
  126.     helper that rsyncs the changes in the aufs chroot back to the
  127.     real system
  128.     """
  129.     for d in systemdirs:
  130.         if not os.path.exists(d):
  131.             continue
  132.         # its important to have the "/" at the end of source
  133.         # and dest so that rsync knows what to do
  134.         cmd = ["rsync","-aHAX","--del","-v", "--progress",
  135.                "/%s/%s/" % (aufs_chroot_dir, d),
  136.                "/%s/" % d]
  137.         logging.debug("running: '%s'" % cmd)
  138.         ret = subprocess.call(cmd)
  139.         logging.debug("rsync back result for %s: %i" % (d, ret))
  140.     return True
  141.  
  142. def doAufsChroot(aufs_rw_dir, aufs_chroot_dir):
  143.     " helper that sets the chroot up and does chroot() into it "
  144.     if not setupAufsChroot(aufs_rw_dir, aufs_chroot_dir):
  145.         return False
  146.     os.chroot(aufs_chroot_dir)
  147.     os.chdir("/")
  148.     return True
  149.  
  150.  
  151. def setupAufsChroot(rw_dir, chroot_dir):
  152.     " setup aufs chroot that is based on / but with a writable overlay "
  153.     # with the chroot aufs we can just rsync the changes back
  154.     # from the chroot dir to the real dirs
  155.     # 
  156.     # (if we run rsync with --backup --backup-dir we could even
  157.     # create something vaguely rollbackable
  158.  
  159.     # get the mount points before the aufs buisiness starts
  160.     mounts = open("/proc/mounts").read()
  161.  
  162.     # aufs mount or bind mount required dirs
  163.     for d in os.listdir("/"):
  164.         d = os.path.join("/",d)
  165.         if os.path.isdir(d):
  166.             if d in systemdirs:
  167.                 logging.debug("bind mounting %s" % d)
  168.                 if not _aufsOverlayMount(d, rw_dir, chroot_dir):
  169.                     return False
  170.             else:
  171.                 logging.debug("overlay mounting %s" % d)
  172.                 if not _bindMount(d, chroot_dir+d, rbind=True):
  173.                     return False
  174.  
  175.     # create binds for the systemdirs
  176.     needs_bind_mount = set()
  177.     for line in map(string.strip, mounts.split("\n")):
  178.         if not line: continue
  179.         (device, mountpoint, fstype, options, a, b) = line.split()
  180.         if (fstype != "aufs" and
  181.             not is_real_fs(fstype) and
  182.             is_submount(mountpoint, systemdirs)):
  183.             logging.debug("found %s that needs bind mount", mountpoint)
  184.             if not _bindMount(mountpoint, chroot_dir+mountpoint):
  185.                 return False
  186.     return True
  187.  
  188. def setupAufs(rw_dir):
  189.     " setup aufs overlay over the rootfs "
  190.     #        * we need to find a way to tell all the existing daemon 
  191.     #          to look into the new namespace. so probably something
  192.     #          like a reboot is required and some hackery in initramfs-tools
  193.     #          to ensure that we boot into a overlay ready system
  194.     #        * this is much less of a issue with the aufsChroot code
  195.     logging.debug("setupAufs")
  196.     if not os.path.exists("/proc/mounts"):
  197.         logging.debug("no /proc/mounts, can not do aufs overlay")
  198.         return False
  199.  
  200.     # verify that there are no submounts of a systemdir and collect
  201.     # the stuff that needs bind mounting (because a aufs does not
  202.     # include sub mounts)
  203.     needs_bind_mount = set()
  204.     needs_bind_mount.add("/var/cache/apt/archives")
  205.     for line in open("/proc/mounts"):
  206.         (device, mountpoint, fstype, options, a, b) = line.split()
  207.         if is_real_fs(fstype) and is_submount(mountpoint, systemdirs):
  208.             logging.warning("mountpoint %s submount of systemdir" % mountpoint)
  209.             return False
  210.         if (fstype != "aufs" and not is_real_fs(fstype) and is_submount(mountpoint, systemdirs)):
  211.             logging.debug("found %s that needs bind mount", mountpoint)
  212.             needs_bind_mount.add(mountpoint)
  213.  
  214.     # aufs mounts do not support stacked filesystems, so
  215.     # if we mount /var we will loose the tmpfs stuff
  216.     # first bind mount varun and varlock into the tmpfs
  217.     for d in needs_bind_mount:
  218.         if not _bindMount(d, rw_dir+"/needs_bind_mount/"+d):
  219.             return False
  220.     # setup writable overlay into /tmp/upgrade-rw so that all 
  221.     # changes are written there instead of the real fs
  222.     for d in systemdirs:
  223.         if not is_aufs_mount(d):
  224.             if not _aufsOverlayMount(d, rw_dir):
  225.                 return False
  226.     # now bind back the tempfs to the original location
  227.     for d in needs_bind_mount:
  228.         if not _bindMount(rw_dir+"/needs_bind_mount/"+d, d):
  229.             return False
  230.  
  231.     # The below information is only of historical relevance:
  232.     #        now what we *could* do to apply the changes is to
  233.     #        mount -o bind / /orig 
  234.     #        (bind is important, *not* rbind that includes submounts)
  235.     # 
  236.     #        This will give us the original "/" without the 
  237.     #        aufs rw overlay  - *BUT* only if "/" is all on one parition
  238.     #             
  239.     #        then apply the diff (including the whiteouts) to /orig
  240.     #        e.g. by "rsync -av /tmp/upgrade-rw /orig"
  241.     #                "script that search for whiteouts and removes them"
  242.     #        (whiteout files start with .wh.$name
  243.     #         whiteout dirs with .wh..? - check with aufs man page)
  244.     return True
  245.  
  246. if __name__ == "__main__":
  247.     logging.basicConfig(level=logging.DEBUG)
  248.     #print setupAufs("/tmp/upgrade-rw")
  249.     print setupAufsChroot("/tmp/upgrade-chroot-rw",
  250.                           "/tmp/upgrade-chroot")
  251.